#!/usr/bin/python3
# ******************************************************************************
# Copyright (c) Huawei Technologies Co., Ltd. 2020-2020. All rights reserved.
# licensed under the Mulan PSL v2.
# You can use this software according to the terms and conditions of the Mulan PSL v2.
# You may obtain a copy of Mulan PSL v2 at:
#     http://license.coscl.org.cn/MulanPSL2
# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR
# PURPOSE.
# See the Mulan PSL v2 for more details.
# ******************************************************************************/
from abc import ABCMeta, abstractmethod
from requests.auth import HTTPBasicAuth
import xmltodict
from conf import config
from logger import logger
from . import Api


class BuildABC(Api):
    __meta_class__ = ABCMeta

    @abstractmethod
    def copy_project(self, original_project, target_project, package: str = None):
        """复制目标工程,指定了package时则只复制特定的rpm包"""
        pass

    @abstractmethod
    def project_meta(self, project):
        """获取工程的meta,检测工程是否存在"""
        pass

    @abstractmethod
    def modify_project_meta(self, project, meta: dict):
        """
        工程不存在就创建工程
        工程在修改一个工程的_meta文件主要增加Repositories
        :param meta:is a dict
                {
                    "project": "openEuler:Test",
                    "title": None,
                    "description": None,
                    "person": {"userid": "obs-main", "role": "maintainer"},
                    "publish": "disable/enable",
                    "repository": [
                        {
                            "name": "standard_x86_64",
                            "arch": "x86_64",
                            "path": ["openEuler:Epol", "openEuler:Mainline"],
                        }
                    ],
                }
        """
        pass

    @abstractmethod
    def build_log(self, project, package, arch):
        """编译的log日志连接地址"""
        pass

    @abstractmethod
    def del_project(self, project, package: str = None):
        """删除工程或工程下的某一个软件包"""
        pass

    @abstractmethod
    def set_package_flag(self, project, package, flag, status):
        """设置特定软件包的flag状态"""
        pass

    @abstractmethod
    def build_rpms(self, project, package, repository="standard_x86_64", arch="x86_64"):
        """获取特定repo生成的rpm包"""
        pass

    @abstractmethod
    def published_binarys(self, project, repository="standard_x86_64", arch="x86_64"):
        """发布工程下的所有归档的二进制包"""
        pass


class OpenBuildService(BuildABC):

    host = "https://build.openeuler.org/"

    def __init__(self, account=None, password=None) -> None:
        super(OpenBuildService, self).__init__()
        self.account = account or config.build_env_account
        self.password = password or config.build_env_passwd

    @staticmethod
    def _status(result):
        return (
            "success" if result.get("status", dict()).get("@code") == "ok" else "failed"
        )

    @staticmethod
    def xml_to_dict(xml: str):
        try:
            return xmltodict.parse(xml)
        except Exception as error:
            pass

    @property
    def _server_error(self):
        return dict(status="failed", detail="server error")

    @property
    def _request_error(self):
        return dict(status="failed", detail="request error")

    def _auth(self):
        return HTTPBasicAuth(self.account, self.password)

    def project_meta(self, project):
        """
        获取工程的_meta
        Args:
            project_name (_type_): _description_
        """
        url = f"{self.host}source/{project}/_meta"
        response = self._get(url, auth=self._auth(), text=True)
        if not response:
            logger.error(f"Failed to get the meta of the project: {url}.")
            return self._request_error
        meta_result = self.xml_to_dict(response)
        if not meta_result:
            return self._server_error
        project_info = meta_result.get("project", dict())
        meta = {
            "project": project_info.get("@name"),
            "title": project_info.get("title"),
            "description": project_info.get("description"),
            "publish": list(project_info.get("publish").keys())[-1],
            "repository": dict(),
        }
        repositorys = project_info.get("repository", dict())
        if isinstance(repositorys, dict):
            repositorys = [repositorys]
        for repository in repositorys:
            paths = repository.get("path")
            if isinstance(paths, dict):
                paths = [paths]
            meta["repository"][repository["arch"]] = [
                path.get("@project") for path in paths
            ]

        return dict(status="success", detail=meta)

    def copy_project(self, original_project, target_project, package: str = None):
        """
        拷贝一个工程的到另外一个工程
        如果存在软件包,则需复制rpm至目标工程
        """
        if package:
            values = dict(cmd="branch", oproject=target_project, nodelay=True)
            url = f"{self.host}source/{original_project}/{package}"
        else:
            values = dict(cmd="copy", oproject=original_project, nodelay=True)
            url = f"{self.host}source/{target_project}"
        response = self._post(url, values, auth=self._auth(), text=True)
        if response is None:
            logger.error(f"Failed to copy a project or software package: {url}.")
            return self._request_error
        copy_result = OpenBuildService.xml_to_dict(response)
        if not copy_result:
            return self._server_error

        return dict(
            status=OpenBuildService._status(copy_result),
            detail=copy_result.get("status", dict()).get("summary"),
        )

    def modify_project_meta(self, project, meta: dict):
        url = f"{self.host}source/{project}/_meta"
        repository_xml = ""
        for repository in meta.get("repository", []):
            paths = ""
            for path in repository.get("path", []):
                paths += f"""<path project="{path}" repository="{repository.get('name')}"/>"""
            repository_xml += f"""
                <repository name="{repository.get('name')}">
                    {paths}
                    <arch>{repository.get('arch')}</arch>
                </repository>
            """
        meta_xml = f"""
            <project name="{meta.get('project')}">
                <title>{meta.get('title')}</title>
                <description>{meta.get('description')}</description>
                <person userid="{meta.get('person',dict()).get('userid')}" role="maintainer"/>
                <publish>
                    <{meta.get('publish')}/>
                </publish>
                {repository_xml}
            </project>
        """
        response = self._put(url, meta_xml, text=True, auth=self._auth())
        if response is None:
            logger.error(f"Failed to change the project meta: {url}.")
            return self._request_error
        modify_result = OpenBuildService.xml_to_dict(response)
        if not modify_result:
            return self._server_error

        return dict(
            status=OpenBuildService._status(modify_result),
            detail=modify_result.get("status", dict()).get("summary"),
        )

    def build_log(self, project, package, arch) -> str:
        return f"{self.host}/build/{project}/standard_{arch}/{arch}/{package}/_log"

    def del_project(self, project, package: str = None):
        url = f"{self.host}/source/{project}"
        if package:
            url += f"/{package}"
        response = self._delete(url=url, text=True, auth=self._auth())
        if response is None:
            logger.error(f"Delete project or package error: {url}")
            return self._request_error
        delete_result = OpenBuildService.xml_to_dict(response)
        if not delete_result:
            return self._server_error
        return dict(
            status=OpenBuildService._status(delete_result),
            detail=delete_result.get("status", dict()).get("summary"),
        )

    def set_package_flag(self, project, package, flag, status):
        url = f"{self.host}/source/{project}/{package}"
        values = dict(cmd="set_flag", flag=flag, status=status)
        response = self._post(url=url, data=values, text=True, auth=self._auth())
        if response is None:
            logger.error(f"Delete project or package error: {url}")
            return self._request_error
        set_flag_result = OpenBuildService.xml_to_dict(response)
        if not set_flag_result:
            return self._server_error
        return dict(
            status=OpenBuildService._status(set_flag_result),
            detail=set_flag_result.get("status", dict()).get("summary"),
        )

    def published_binarys(self, project, repository="standard_x86_64", arch="x86_64"):
        """
        发布工程下的所有归档的二进制包
        """
        url = f"{self.host}/published/{project}/{repository}/{arch}"
        response = self._get(url, text=True, auth=self._auth())
        if response is None:
            logger.error(f"Get published project binarys error: {url}")
            return self._request_error
        binary_result = OpenBuildService.xml_to_dict(response)
        if not binary_result:
            return self._server_error
        return dict(
            status="success",
            detail=[
                packages["@name"]
                for packages in binary_result.get("directory", dict()).get("entry", [])
            ],
        )

    def build_rpms(self, project, package, repository="standard_x86_64", arch="x86_64"):
        """
        获取特定repo生成的rpm包
        """
        url = f"{self.host}/build/{project}/{repository}/{arch}/{package}"
        response = self._get(url, text=True, auth=self._auth())
        if response is None:
            logger.error(f"Get build rpm error: {url}")
            return self._request_error
        rpms_result = OpenBuildService.xml_to_dict(response)
        if not rpms_result:
            return self._server_error

        return dict(
            status="success",
            detail=[
                rpm["@filename"]
                for rpm in rpms_result.get("binarylist", dict()).get("binary", [])
                if rpm["@filename"].endswith("noarch.rpm")
            ],
        )
